try:
    from functools import wraps
except ImportError:
    from django.utils.functional import wraps
from django import forms
from django import template
from django.contrib import admin
from django.contrib.admin.util import unquote
from django.contrib.admin.options import InlineModelAdmin, IncorrectLookupParameters
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.db import models
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.utils import simplejson
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext as _

from curator import widgets
from curator.util import get_yui, get_yui_css

class ModelAdmin(admin.ModelAdmin):
    """
    A subclass to Django 1.0 ModelAdmin.
    Use this with django.contrib.admin.AdminSite or if you want to utilize
    the more flexible layout option use sorl.curator.AdminSite.
    `position_field` should be a model field for ordering of rows.
    `order_with_respect_to` is a field to which you would like to order
    with respect to. It can be either a foreignkey or field with choices.
    """

    position_field = None
    order_with_respect_to = None
    description = ''
    change_form_template = 'curator/change_form.html'
    change_list_template = 'curator/change_list.html'
    change_list_popup_template = None
    delete_confirmation_template = 'curator/delete_confirmation.html' 
    object_history_template = 'curator/object_history.html'
    reorder_template = 'curator/reorder.html'

    def update_extra_context(f):
        """
        Decorator to set the required variables to be available in templates.
        If layout was not set in AdminSite we use app_label as fallback.
        """
        @wraps(f)
        def wrapper(self, *args, **kwargs):
            extra_context = kwargs.get('extra_context', {})
            if self.admin_site.layout:
                section = self.admin_site.get_section(self.model)
                extra_context['section_id'] = self.admin_site.get_section_id(section)
                extra_context['section_name'] = section[0]
            else:     
                app_label = self.model._meta.app_label
                extra_context['section_id'] = app_label
                extra_context['section_name'] = capfirst(app_label)
            kwargs['extra_context'] = extra_context
            return f(self, *args, **kwargs)
        return wrapper

    def __call__(self, request, url):
        if url == 'reorder':
            return self.reorder_view(request)
        return super(ModelAdmin, self).__call__(request, url)

    def response_add(self, request, *args, **kwargs):
        """
        The only extra thing we do here is to redirect to the list popup
        view if the key _popup_listing is in POST. This is because we want
        to use the popup window to add new entries and come back to the
        list view.
        """
        if request.POST.has_key('_popup_listing'):
            return HttpResponseRedirect('../?pop=1')
        return super(ModelAdmin, self).response_add(request, *args, **kwargs)

    @update_extra_context
    def add_view(self, request, *args, **kwargs):
        """
        We prepare so that if we come from popup listing. We'll come back
        after data is posted.
        """
        extra_context = kwargs.get('extra_context' or {})
        if request.REQUEST.has_key('_popup_listing'):
            extra_context['is_popup_listing'] = True
        kwargs['extra_context'] = extra_context
        try:
            return super(ModelAdmin, self).add_view(request, *args, **kwargs)
        except TypeError:
            # This is only because the UserAdmin.add_view doesn't take
            # extra_context, bummer
            kwargs.pop('extra_context')
            return super(ModelAdmin, self).add_view(request, *args, **kwargs)
            

    @update_extra_context
    def change_view(self, request, *args, **kwargs):
        #TODO ajax save and continue, validation?
        if request.method == "POST":
            if request.is_ajax() and request.POST.has_key('_continue'):
                # put save and continue code here
                pass
        return super(ModelAdmin, self).change_view(request, *args, **kwargs)
        
    @update_extra_context
    def changelist_view(self, request, extra_context=None):
        """
        Check if we can reorder. Have a special popup list view set. Maybe
        to define callbacks for WYSYWYG editors.
        """
        extra_context['can_reorder'] = self.position_field is not None\
                and self.has_change_permission(request)
        if 'pop' in request.GET and self.change_list_popup_template:
            self.change_list_template = self.change_list_popup_template
        return super(ModelAdmin, self).changelist_view(request, extra_context)

    def reorder_save_model(self, obj):
        "Hook for saving instances when reordering."
        obj.save() 
    
    @update_extra_context
    def reorder_view(self, request, extra_context=None):
        """
        View for reordering objects.
        """
        # TODO add log entry
        opts = self.model._meta
        app_label = opts.app_label
        if not self.has_change_permission(request, None):
            raise PermissionDenied
        if self.position_field is None:
            raise ImproperlyConfigured("ModelAdmin class position_field is None.")
        
        if request.method == 'POST' and request.is_ajax()\
                and request.POST.has_key('order'):
            order = simplejson.loads(request.POST['order'])
            for key, object_pks in order.items():
                for i,object_pk in enumerate(object_pks):
                    obj = self.queryset(request).get(pk=object_pk)
                    if self.order_with_respect_to is not None:
                        field = self.model._meta.get_field(self.order_with_respect_to)
                        if field.rel:
                            key_value = field.rel.to._default_manager.get(pk=key)
                        else:
                            key_value = key
                        setattr(obj, self.order_with_respect_to, key_value)
                    setattr(obj, self.position_field, i)
                    self.reorder_save_model(obj)
            return HttpResponse(_('Reordered %s.') % opts.verbose_name_plural,
                                mimetype="text/javascript")
        else:
            from django.conf import settings
            js = get_yui('container', 'json')
            js.extend([
                '%sjs/curator/ddlist.js' % settings.ADMIN_MEDIA_PREFIX,
                '%sjs/curator/reorder.js' % settings.ADMIN_MEDIA_PREFIX,
            ])
            media = forms.Media(js=js)
            if self.order_with_respect_to is not None:
                field = self.model._meta.get_field(self.order_with_respect_to)
                choices = field.get_choices()
                choices.pop(0) # first seems always to be ('', '---------')
                sections = []
                for key, name in choices:
                    object_list = self.queryset(request)\
                        .filter(**{self.order_with_respect_to: key})\
                        .order_by(self.position_field)
                    # this will respect the queryset filtering
                    if object_list:
                        sections.append({
                            'key': key,
                            'name': name,
                            'object_list': object_list
                        })
            else:
                name = opts.verbose_name.capitalize()
                object_list = self.queryset(request).order_by(self.position_field)
                sections = [{'key': None, 'name': name, 'object_list': object_list}]
            context = {
                'title': _('Reorder %s') % force_unicode(self.opts.verbose_name_plural),
                'opts': opts,
                'sections': sections,
                'media': media,
                'root_path': self.admin_site.root_path,
            }
            context.update(extra_context or {})
            return render_to_response(self.reorder_template, context,
                    context_instance=template.RequestContext(request))

    @update_extra_context
    def history_view(self, *args, **kwargs):
        return super(ModelAdmin, self).history_view(*args, **kwargs)

    @update_extra_context
    def delete_view(self, *args, **kwargs):
        return super(ModelAdmin, self).delete_view(*args, **kwargs)

    def formfield_for_dbfield(self, db_field, **kwargs):
        "Some small changes made to these fields' widgets'"
        if isinstance(db_field, models.ImageField):
            kwargs['widget'] = widgets.AdminImageWidget
        elif isinstance(db_field, models.FileField):
            kwargs['widget'] = widgets.AdminFileWidget
        else:
            return super(ModelAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        return db_field.formfield(**kwargs)


class StackedInline(InlineModelAdmin):
    template = 'curator/edit_inline/stacked.html'

class TabularInline(InlineModelAdmin):
    template = 'curator/edit_inline/tabular.html'
